#include "amazon.h"
#include "object.h"
#include "util.h"
#include <openssl/hmac.h>
#include <curl/curl.h>
#include <curl/types.h>
#include <curl/easy.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include "object_dir.h"
#include "crypt.h"
#include <assert.h>

//TODO: Fix the lengths
static struct {
  char bucketname[100];
  char s3_end_point[200];
  char access_key_id[50];
  char secret_access_key[50];
} config;

struct request_params {
  char url[200];
  char date[50];
  char signed_string[29];
};


static char codes[] =
      { 'A', 'B', 'C', 'D', 'E',
        'F', 'G', 'H', 'I', 'J',
        'K', 'L', 'M', 'N', 'O',
        'P', 'Q', 'R', 'S', 'T',
        'U', 'V', 'W', 'X', 'Y',
        'Z', 'a', 'b', 'c', 'd',
        'e', 'f', 'g', 'h', 'i',
        'j', 'k', 'l', 'm', 'n',
        'o', 'p', 'q', 'r', 's',
        't', 'u', 'v', 'w', 'x',
        'y', 'z', '0', '1', '2',
        '3', '4', '5', '6', '7',
        '8', '9', '+', '/' };


/* It is the responsibility of the caller to allocate sufficient space */
void base64_encode (unsigned char *src, int src_len, char *dst) {

  int i=0, o=0;
  while (i < src_len) {

    unsigned char a = 0, b = 0, c = 0;
    a = src[i];
    if (i+1 < src_len)
      b = src[i+1];
    if (i+2 < src_len)
      c = src[i+2];

    uint32_t cat = 0;
    cat |= c;
    cat |= (b<<8);
    cat |= (a<<16);

    dst[o] = codes [0x0000003f & (cat>>18)];
    dst[o+1] = codes [0x0000003f & (cat>>12)];
    dst[o+2] = codes [0x0000003f & (cat>>6)];
    dst[o+3] = codes [0x0000003f & cat];

    /* Put ='s */
    if (i+1 >= src_len) {
      dst[o+2] = dst[o+3] = '=';
    } else if (i+2 >= src_len) {
      dst[o+3] = '=';
    }
    i += 3;
    o += 4;
  }

}

/*
  Based on 
  http://stackoverflow.com/questions/1636333/download-file-using-libcurl-in-c-c
*/

struct var_buf {
  char *data;
  int pos; 
  int max;      /* Max available space, or max available data */
};

size_t write_data (void *ptr, size_t size, size_t nmemb, void *stream) {

  struct var_buf *res = (struct var_buf *)stream;

  if (res->pos + (size*nmemb) >= res->max) {
    /* Too small buffer, can't pause. We have to stop */
    return -1;
  }

  memcpy (res->data + res->pos, ptr, size*nmemb);
  res->pos += size *nmemb;


  return size*nmemb;
}

size_t read_data (void *ptr, size_t size, size_t nmemb, void *stream) {

  struct var_buf *res = (struct var_buf *)stream;

  if (res->pos >= res->max) {
    /* End of file */
    return 0;
  }

  /* Amount of data we need to copy in this call*/
  int to_copy = size*nmemb > (res->max - res->pos) ? 
                        (res->max - res->pos) : size*nmemb;

  memcpy (ptr, res->data + res->pos, to_copy);
  res->pos += to_copy;

  return to_copy;
}

int load_config (const char *filename) {

  FILE *conffile = fopen (filename, "r");
  if (conffile != NULL) {

    fscanf (conffile, "BUCKETNAME = %s\n", config.bucketname);
    fscanf (conffile, "S3_END_POINT = %s\n", config.s3_end_point);
    fscanf (conffile, "ACCESS_KEY_ID = %s\n", config.access_key_id);
    fscanf (conffile, "SECRET_ACCESS_KEY = %s", config.secret_access_key);
    fclose (conffile);
    return 0;
  } 
  else 
    return -1;
}

int get_cloud_object (char *object_name, aeskey *k) {

  debug ("CLOUD<<< %s\n", object_name);

  CURL *curl;
  FILE *fp;

  struct request_params req;

  strcpy (req.url, object_name);

  time_t t;
  time (&t);
  struct tm *ts = gmtime (&t);
  char *wday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; 
  sprintf (req.date, "%s, %d %s %d %d:%d:%d +0000", 
                wday[ts->tm_wday], ts->tm_mday, months[ts->tm_mon], 
                1900+ts->tm_year, ts->tm_hour, ts->tm_min, ts->tm_sec);

  char outfilename[200];
  sprintf (outfilename, "%s/%s", PREFIX, req.url);

  curl = curl_easy_init ();
  if (curl) {

    char *mem_buf1 = malloc (MAX_DATA_OBJ + (AES_BLOCK_SIZE/8));
    struct var_buf b;
    b.data = mem_buf1;
    b.pos = 0;
    b.max = MAX_DATA_OBJ + (AES_BLOCK_SIZE/8);
    
    struct curl_slist* slist = NULL;

    char s[1000];

    /* Append the date */
    strcpy (s, "Date: ");
    strcat (s, req.date);
    slist = curl_slist_append (slist, s); 

    /* Get the string_to_sign in place */
    sprintf (s, "GET\n\n\n%s\n/%s/%s", req.date, config.bucketname, req.url);

    unsigned char md[20];
    unsigned int md_len = 20;
    HMAC (EVP_sha1(), (unsigned char *)config.secret_access_key, 
                       strlen (config.secret_access_key), (unsigned char *)s, 
                       strlen (s), md, &md_len);
    memset (req.signed_string, 0, 29);
    base64_encode (md, md_len, req.signed_string);

    sprintf (s, "Authorization: AWS %s:%s", 
                            config.access_key_id, req.signed_string);

    slist = curl_slist_append (slist, s);
    slist = curl_slist_append (slist, "Accept: ");

    curl_easy_setopt (curl, CURLOPT_HTTPHEADER, slist);
    curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);

    sprintf (s, "http://%s.%s/%s", config.bucketname, 
                                   config.s3_end_point, req.url); 

    curl_easy_setopt (curl, CURLOPT_URL, s);
    curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt (curl, CURLOPT_WRITEDATA, &b);
    curl_easy_perform (curl);

    curl_slist_free_all (slist);
    curl_easy_cleanup (curl);

    char *mem_buf2 = malloc (MAX_DATA_OBJ+(AES_BLOCK_SIZE/8));
    int actual_length;

    if (b.pos != 0) {
      aes_decrypt (k->key, k->iv, (unsigned char *)b.data, b.pos, 
                    (unsigned char *)mem_buf2, &actual_length);
    } else {
      actual_length = 0;
    }

    fp = fopen (outfilename, "wb");
    fwrite (k->key, 32, 1, fp);
    fwrite (k->iv, 32, 1, fp);
    fwrite (mem_buf2, actual_length, 1, fp);
    fclose (fp);

    assert (actual_length <= MAX_DATA_OBJ);

    free (mem_buf1);
    free (mem_buf2);

    return 0;
  } else 
    return -1;
}

int put_cloud_object (char *object_name) {

  debug ("CLOUD>>> %s\n", object_name);

  CURL *curl;
  FILE *fp;

  struct request_params req;

  strcpy (req.url, object_name);

  time_t t;
  time (&t);
  struct tm *ts = gmtime (&t);
  char *wday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; 
  sprintf (req.date, "%s, %d %s %d %d:%d:%d +0000", 
                wday[ts->tm_wday], ts->tm_mday, months[ts->tm_mon], 
                1900+ts->tm_year, ts->tm_hour, ts->tm_min, ts->tm_sec);

  char infilename[200];
  sprintf (infilename, "%s/%s", PREFIX, req.url);


  curl = curl_easy_init ();
  if (curl) {

    char *mem_buf1 = malloc (MAX_DATA_OBJ);
    char *mem_buf2 = malloc (MAX_DATA_OBJ + (AES_BLOCK_SIZE/8));

    struct var_buf b;
    b.data = mem_buf2;
    b.pos = 0;
    fp = fopen (infilename, "rb");

    /* Get file size */
    fseek (fp, 0L, SEEK_END);
    size_t filesize = ftell (fp)-64;
    fseek (fp, 0L, SEEK_SET);
 
    aeskey k;
    fread (k.key, 32, 1, fp);
    fread (k.iv, 32, 1, fp);

    fread (mem_buf1, filesize, 1, fp);
    fclose (fp);

    if (filesize != 0) {
      aes_encrypt (k.key, k.iv, (unsigned char *)mem_buf1, filesize, 
                      (unsigned char *)b.data, &(b.max));
    } else {
      b.max = 0;
    }

    struct curl_slist* slist = NULL;

    char s[1000];

    /* Append the date */
    strcpy (s, "Date: ");
    strcat (s, req.date);
    slist = curl_slist_append (slist, s); 

    /* Get the string_to_sign in place */
    sprintf (s, "PUT\n\n\n%s\n/%s/%s", req.date, config.bucketname, req.url);

    unsigned char md[20];
    unsigned int md_len = 20;
    HMAC (EVP_sha1(), (unsigned char *)config.secret_access_key, 
                       strlen (config.secret_access_key), (unsigned char *)s, 
                       strlen (s), md, &md_len);
    memset (req.signed_string, 0, 29);
    base64_encode (md, md_len, req.signed_string);

    sprintf (s, "Authorization: AWS %s:%s", 
                            config.access_key_id, req.signed_string);

    slist = curl_slist_append (slist, s);
    slist = curl_slist_append (slist, "Expect: ");


    curl_easy_setopt (curl, CURLOPT_HTTPHEADER, slist);
    curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);

    sprintf (s, "http://%s.%s/%s", config.bucketname, 
                                   config.s3_end_point, req.url); 

    curl_easy_setopt (curl, CURLOPT_URL, s);
    curl_easy_setopt (curl, CURLOPT_UPLOAD, 1);
    curl_easy_setopt (curl, CURLOPT_INFILESIZE, b.max);
    curl_easy_setopt (curl, CURLOPT_READFUNCTION, read_data);
    curl_easy_setopt (curl, CURLOPT_READDATA, &b);
    curl_easy_perform (curl);

    curl_slist_free_all (slist);
    curl_easy_cleanup (curl);
    
    
    /* We should not need to add the object to object_dir  
      add_to_object_dir (req.url); */
    set_cloud_field (req.url, 1);

    free (mem_buf1);
    free (mem_buf2);

    return 0;
  } else 
    return -1;
}


static int count = 0;
/* Just pushes the names generated by xml parsing to allocated mem */
int stack_push (void *list, char *name, int max_length) {
  if ((count+1)*ID_STRING_LENGTH < max_length) {
    strncpy (((char *)list)+(count*ID_STRING_LENGTH), name, ID_STRING_LENGTH);
    count++;
    return 0;
  } else
    return -1;  /* Stack overflow */
}

void populate_element_names (xmlNode *a_node, void *list, int max_length) {

  xmlNode *cur_node = NULL;

  for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
    if (strcmp ((const char *)(cur_node->name), "Key") == 0) {
      /* 
        printf ("node type: Element, name: %s, value: %s\n", 
                      cur_node->name, xmlNodeGetContent(cur_node) );
      */
        stack_push (list, (char *)xmlNodeGetContent(cur_node), max_length);
    }

    populate_element_names (cur_node->children, list, max_length);
  }
}

int list_cloud_object (char *prefix, int max_length, void *list) {

  //TODO: too frustrated. Let's just write the input data to
  //      a temp file, and read it using libxml2

  CURL *curl;

  struct request_params req;

  sprintf (req.url, "?prefix=%s&max-keys=10000000", prefix);

  time_t t;
  time (&t);
  struct tm *ts = gmtime (&t);
  char *wday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; 
  sprintf (req.date, "%s, %d %s %d %d:%d:%d +0000", 
                wday[ts->tm_wday], ts->tm_mday, months[ts->tm_mon], 
                1900+ts->tm_year, ts->tm_hour, ts->tm_min, ts->tm_sec);

  curl = curl_easy_init ();
  if (curl) {

    char *mem_buf = malloc (1000000);
    struct var_buf b;
    b.data = mem_buf;
    b.pos = 0;
    b.max = 1000000;

    struct curl_slist* slist = NULL;

    char s[1000];

    /* Append the date */
    strcpy (s, "Date: ");
    strcat (s, req.date);
    slist = curl_slist_append (slist, s); 

    /* Get the string_to_sign in place */
    sprintf (s, "GET\n\n\n%s\n/%s/", req.date, config.bucketname);

    unsigned char md[20];
    unsigned int md_len = 20;
    HMAC (EVP_sha1(), (unsigned char *)config.secret_access_key, 
                       strlen (config.secret_access_key), (unsigned char *)s, 
                       strlen (s), md, &md_len);
    memset (req.signed_string, 0, 29);
    base64_encode (md, md_len, req.signed_string);

    sprintf (s, "Authorization: AWS %s:%s", 
                            config.access_key_id, req.signed_string);

    slist = curl_slist_append (slist, s);
    slist = curl_slist_append (slist, "Accept: ");

    curl_easy_setopt (curl, CURLOPT_HTTPHEADER, slist);
    curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);

    sprintf (s, "http://%s.%s/%s", config.bucketname, 
                                   config.s3_end_point, req.url); 

    curl_easy_setopt (curl, CURLOPT_URL, s);
    curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt (curl, CURLOPT_WRITEDATA, &b);
    curl_easy_perform (curl);

    curl_slist_free_all (slist);
    curl_easy_cleanup (curl);

    /* Now comes the time to do the xml parsing */

    
    /* Just call it, don't argue */
    LIBXML_TEST_VERSION

    xmlDoc *doc = NULL;
    xmlNode *root_element = NULL;

    doc = xmlReadMemory (b.data, b.pos, "noname.xml", NULL, 0);

    if (doc == NULL) {
      printf ("We are screwed\n");
      return -1;
    }

    root_element = xmlDocGetRootElement (doc);
   
    count=0; 
    populate_element_names (root_element, list, max_length);

    xmlFreeDoc (doc);
    xmlCleanupParser ();

    free (mem_buf);

    return count;
  } else 
    return -1;
}

